package net.fenghaitao;

import lombok.SneakyThrows;
import net.fenghaitao.annotation.ExcelEntity;
import net.fenghaitao.domain.BlockNameResolver;
import net.fenghaitao.domain.ExcelVerifyHandlerResult;
import net.fenghaitao.enums.DataDirection;
import net.fenghaitao.exception.AutoExcelException;
import net.fenghaitao.handler.AutoExcelValidHandler;
import net.fenghaitao.managers.CellManager;
import net.fenghaitao.parameters.ImportPara;
import net.fenghaitao.utils.CellUtil;
import net.fenghaitao.utils.Convert;
import net.fenghaitao.utils.ReflectUtils;
import net.fenghaitao.utils.ValidationUtil;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @author ：HuangXiong
 * @date ：Created in 2021/1/11 14:03
 * @description：excel 读取
 * @modified By：
 * @version:
 */
public class ExcelReader {
    private ExcelReader() {
    }

    /**
     * Import data from excel
     *
     * @param fileName    file name to read
     * @param importParas the parameter for import
     */
    public static HashMap<String, List<HashMap<String, Object>>> read(String fileName, List<ImportPara> importParas) throws Exception {
        if (!(new File(fileName)).exists())
            throw new Exception("File not found: " + fileName);
        try (FileInputStream fs = new FileInputStream(fileName)) {
            return read(fs, importParas);
        }

    }

    /**
     * Import data from excel
     */
    public static HashMap<String, List<HashMap<String, Object>>> read(FileInputStream fileInputStream, List<ImportPara> importParas) throws Exception {
        Workbook workbook = new XSSFWorkbook(fileInputStream);
        return read(workbook, importParas);
    }

    public static <T> List<T> readByDataSource(String fileName, List<ImportPara> importParas, Class<T> clazz, AutoExcelValidHandler<T> validHandler) throws Exception {
        return readByDataSource(fileName, importParas, clazz, clazz.getSimpleName(), validHandler);
    }

    public static <T> List<T> readByDataSource(String fileName, List<ImportPara> importParas, Class<T> clazz, String dataSourceName,
                                               AutoExcelValidHandler<T> validHandler) throws Exception {
        if (!(new File(fileName)).exists())
            throw new IOException("File not found: " + fileName);

        try (FileInputStream fs = new FileInputStream(fileName)) {

            Workbook workbook = fileName.endsWith("xlsx") ? new XSSFWorkbook(fs) : new HSSFWorkbook(fs);
            return ExcelReader.readByDataSource(workbook, importParas, clazz, dataSourceName, validHandler);
        }
    }

    /**
     * Import data from excel
     */
    public static <T> List<T> readByDataSource(Workbook workbook, List<ImportPara> importParas, Class<T> clazz, String dataSourceName,
                                               AutoExcelValidHandler<T> validHandler
    ) {
        List<T> dataSet = new ArrayList<T>();
        List<BlockNameResolver<ImportPara>> blockNameResolvers = CellUtil.resolveCellNames(workbook, importParas);

        for (BlockNameResolver<ImportPara> blockNameResolver : blockNameResolvers) {

            if (Objects.isNull(blockNameResolver.getPara()) || !dataSourceName.equalsIgnoreCase(blockNameResolver.getPara().getDataSourceName()))
                continue;

            Sheet sheet = workbook.getSheet(blockNameResolver.getSheetName());
            Map<String, CellManager> fieldNameCells = blockNameResolver.getFieldNameCells();
            int rowNum = fieldNameCells.entrySet().iterator().next().getValue().getRowIndex();
            // 获取字段信息
            Map<String, Field> fieldMap = ReflectUtils.getFieldMap(clazz);
            //fill dataTable
            switch (blockNameResolver.getPara().getDataDirection()) {
                case Down:
                    for (int i = rowNum, size = sheet.getLastRowNum(); i <= size; ++i) {
                        Row row = sheet.getRow(i);
                        //default null if the row doesn't contain data
                        Optional.ofNullable(row).ifPresent((r)->
                                dataSet.add(getRowData(clazz, fieldNameCells, sheet, r, fieldMap, blockNameResolver, validHandler))
                        );

                    }
                    break;
                case Right:
                    // TODO:::
                    throw new AutoExcelException("This function is not implemented");
                default:
                    dataSet.add(getRowData(clazz, fieldNameCells, sheet, null, fieldMap, blockNameResolver, validHandler));
                    break;
            }
        }
        return dataSet;
    }

    @SneakyThrows
    private static <T> T getRowData(Class<T> clazz, Map<String, CellManager> fieldNameCells,
                                    Sheet sheet, Row dfRow, Map<String, Field> fieldMap
            , BlockNameResolver<ImportPara> blockNameResolver,
                                    AutoExcelValidHandler<T> validHandler) {
        T dataRow = clazz.newInstance();
        for (Map.Entry<String, CellManager> entrySet : fieldNameCells.entrySet()) {
            String originalFieldName = entrySet.getKey();
            CellManager cellManager = entrySet.getValue();

            dfRow = Objects.isNull(dfRow) ? sheet.getRow(cellManager.getRowIndex()) : dfRow;
            //default null if the row doesn't contain data
            if (dfRow == null) continue;

            Cell cell = dfRow.getCell(cellManager.getColIndex());
            //default null if the cell doesn't contain data
            if (Objects.nonNull(cell)) {
                Field declaredField = fieldMap.get(originalFieldName);
                if (Objects.nonNull(declaredField)) {
                    ExcelEntity annotation = declaredField.getAnnotation(ExcelEntity.class);
                    Object fieldVal;
                    if (Objects.nonNull(annotation)) {
                        BlockNameResolver<ImportPara> childBlockNameResolver = blockNameResolver.getChildBlockNameResolver();
                        if (Objects.isNull(childBlockNameResolver)) throw new AutoExcelException("childBlockNameResolver can`t be null!!!");

                        Class<? extends AutoExcelValidHandler> vaildHandlerClass = annotation.vaildHandler();
                        AutoExcelValidHandler childValidHandler = vaildHandlerClass.equals(AutoExcelValidHandler.class)
                                ? null : vaildHandlerClass.newInstance();
                        switch (annotation.dataSourceType()) {
                            case List:
                                List list = new ArrayList();
                                Type genericType = declaredField.getGenericType();
                                ParameterizedType pt = (ParameterizedType) genericType;
                                // 获取泛型
                                final Class<?> tClass = annotation.genericType().equals(Object.class)?
                                        (Class<?>) pt.getActualTypeArguments()[0] : annotation.genericType();

                                CellManager cellManager1 = childBlockNameResolver.getFieldNameCells().values().stream().findFirst().get();

                                for (int i = cellManager1.getRowIndex(), size = sheet.getLastRowNum(); i <= size; ++i) {
                                    Row childRow = sheet.getRow(i);
                                    //default null if the row doesn't contain data
                                    Optional.ofNullable(childRow).ifPresent((r)->
                                        list.add(
                                                getRowData(tClass, childBlockNameResolver.getFieldNameCells(),
                                                        sheet, childRow, ReflectUtils.getFieldMap(tClass), childBlockNameResolver,childValidHandler )
                                        )
                                    );
                                }
                                fieldVal = list;
                                break;
                            default:
                                Class<?> defTClass = annotation.genericType();
                                // 获取泛型
                                if (defTClass.equals(Object.class)) {
                                    defTClass = declaredField.getDeclaringClass();
                                }
                                fieldVal = getRowData(defTClass, childBlockNameResolver.getFieldNameCells(),
                                        sheet, null, ReflectUtils.getFieldMap(defTClass), childBlockNameResolver, childValidHandler);
                                break;
                        }
                    } else {
                        fieldVal = CellUtil.getValue(cell);
                        fieldVal = Convert.convert(declaredField.getType(), fieldVal);
                    }
                    ReflectUtils.invokeSetter(dataRow, originalFieldName, fieldVal);

                }
            }
        }
        ExcelVerifyHandlerResult validResult = ValidationUtil.validateBean(dataRow);
        int rowNum = dfRow.getRowNum() + 1;
        DataDirection dataDirection = blockNameResolver.getIsChild()
                ? DataDirection.Down : blockNameResolver.getPara().getDataDirection();
        validResultHandler(validResult, dataDirection, rowNum);
        // 校验  调用handler 来校验
        Optional.ofNullable(validHandler).ifPresent(v->validResultHandler(v.doValid(dataRow), rowNum));
        return dataRow;
    }


    private static HashMap<String, List<HashMap<String, Object>>> read(Workbook workbook, List<ImportPara> importParas) throws Exception {
        List<BlockNameResolver<ImportPara>> blockNameResolvers = CellUtil.resolveCellNames(workbook, importParas);
        HashMap<String, List<HashMap<String, Object>>> dataSet = new HashMap<>(blockNameResolvers.size());
        for (BlockNameResolver<ImportPara> blockNameResolver : blockNameResolvers) {
            if (blockNameResolver.getPara() == null) continue;

            List<HashMap<String, Object>> dataTable = new ArrayList<>();
            Sheet sheet = workbook.getSheet(blockNameResolver.getSheetName());
            Map<String, CellManager> fieldNameCells = blockNameResolver.getFieldNameCells();
            Set<Map.Entry<String, CellManager>> fieldNameCellEntrySet = fieldNameCells.entrySet();
            int rowNum = fieldNameCellEntrySet.iterator().next().getValue().getRowIndex();

            //fill dataTable
            switch (blockNameResolver.getPara().getDataDirection()) {
                case Down:
                    for (int i = rowNum; i <= sheet.getLastRowNum(); ++i) {
                        Row row = sheet.getRow(i);
                        if (row == null) continue;      //default null if the row doesn't contain data

                        HashMap<String, Object> dataRow = new HashMap<>(fieldNameCellEntrySet.size());
                        for (Map.Entry<String, CellManager> entrySet : fieldNameCellEntrySet) {
                            String originalFieldName = blockNameResolver.getFieldNameMap().get(entrySet.getKey());
                            CellManager cellManager = entrySet.getValue();
                            Cell cell = row.getCell(cellManager.getColIndex());
                            Optional.ofNullable(cell).ifPresent( c ->
                                    dataRow.put(originalFieldName, CellUtil.getValue(c))
                            );
                        }
                        dataTable.add(dataRow);
                    }
                    break;

                case Right:
                    throw new Exception("This function is not implemented");

                default:
                    HashMap<String, Object> dataRow2 = new HashMap<>();
                    for (Map.Entry<String, CellManager> entrySet : fieldNameCellEntrySet) {
                        String originalFieldName = blockNameResolver.getFieldNameMap().get(entrySet.getKey());
                        CellManager cellManager = entrySet.getValue();
                        Row row = sheet.getRow(cellManager.getRowIndex());
                        if (row == null) continue;      //default null if the row doesn't contain data

                        Cell cell = row.getCell(cellManager.getColIndex());
                        Optional.ofNullable(cell).ifPresent( c ->
                                dataRow2.put(originalFieldName, CellUtil.getValue(c))
                        );
                    }
                    dataTable.add(dataRow2);
                    break;
            }
            dataSet.put(blockNameResolver.getOriginalDataSourceName(), dataTable);
        }
        return dataSet;
    }


    public static void validResultHandler(ExcelVerifyHandlerResult validResult, int rowNum) {
        validResultHandler(validResult, DataDirection.Down, rowNum);
    }

    public static void validResultHandler(ExcelVerifyHandlerResult validResult, DataDirection dataDirection, int rowNum) {
        if (Objects.nonNull(validResult) && validResult.hasErrors()) {
            String errorMsg = "";
            switch (dataDirection) {
                case Down:
                    errorMsg = String.format(
                            "%s 行 : %s", rowNum, validResult.getErrors()
                    );
                    break;
                case Right:
                    //TODO:::
                    break;
                default:
                    errorMsg = validResult.getErrors();
                    break;
            }
            throw new AutoExcelException(errorMsg);
        }
    }
}
